import { ALLOWABLE_CHILD_NODE_DEFAULT_NS, getAllowableChildNode, setAllowableChildNode } from './allowable-child'
import { idAttrSetter } from './attribute'
import Command from './command'
import { moAttribute, moConnect, moDisconnect } from './observer'
import { eachArray } from './utils'

export class PEElement extends HTMLElement {
  static nodeName: string = 'pe-base'

  /**
   * 当前节点所在文档的根
   */
  public root: Document | ShadowRoot = document

  /**
   * 是否需要自动生成 id attribute
   */
  public needID: boolean = false

  /**
   * 一些特殊数据的命名空间。例如：id 的前缀00
   */
  public idns: string = 'pe'

  /**
   * 配置项的命名空间
   */
  public ns: string = ALLOWABLE_CHILD_NODE_DEFAULT_NS

  /**
   * 替代 document.execCommand
   */
  public command = new Command(this)

  constructor() {
    super()
    // 根据需求动态监听 attribute 变化
    const attributeChanged = Reflect.get(this, 'attributeChanged')
    if (typeof attributeChanged === 'function') {
      moAttribute(this, (mutations) => {
        eachArray(mutations, (mutation) => {
          const name = mutation.attributeName as string
          Reflect.apply(attributeChanged, this, [name, this.getAttribute(name), mutation.oldValue])
        })
      })
    }
  }
  /* --------------------------------------------------------------- 内置方法 --------------------------------------------------------------- */

  /**
   * 调用未知的内置方法
   * @param name callback name
   * @param args callback arguments
   */
  public callback(name: string, args?: any[] | ((this: PEElement) => any[])) {
    const cb = Reflect.get(this, name)
    if (typeof cb === 'function') {
      args = args ? (Array.isArray(args) ? args : args.call(this)) : []
      Reflect.apply(cb, this, args)
    }
  }

  public init() {
    const root = this.getRootNode()
    if (root instanceof ShadowRoot) {
      // 文档根对象
      this.root = root
      // 当前文档的命名空间
      this.ns = Reflect.get(root.host, 'ns') ?? ALLOWABLE_CHILD_NODE_DEFAULT_NS
    }
  }

  /* --------------------------------------------------------------- 生命周期 --------------------------------------------------------------- */

  private connectedCallback() {
    this.init()

    // 连接 MutationObserver 监听
    moConnect(this)

    // 设置 id attribute
    this.needID && idAttrSetter(this)

    // 执行子类的 connectedCallback 函数
    this.callback('connected')
  }

  private disconnectedCallback() {
    // 断开 MutationObserver 监听
    moDisconnect(this)

    // 执行子类的 disconnectedCallback 函数
    this.callback('disconnected')
  }

  /* --------------------------------------------------------- 需要子类继承重写的方法 --------------------------------------------------------- */

  /**
   * 节点优化的具体实现，用于子类实现
   */
  // public adjustCallback() {}

  /**
   * connectedCallback 的简写，用于子类实现
   */
  // protected connected() {}

  /**
   * disconnectedCallback 的简写，用于子类实现
   */
  // protected disconnected() {}

  /**
   * attributeChangedCallback 的简写，用于子类实现
   * 默认只监听 allowableCommand 中存在的 attribute name。
   * @param name
   * @param newValue
   * @param oldValue
   */
  // protected attributeChanged(name: string, newValue: string | null, oldValue: string | null) {}
}

export class PEShadowElement extends PEElement {
  /**
   * shadowRoot 的简写
   */
  public shadow: ShadowRoot

  public constructor() {
    super()
    this.shadow = this.attachShadow({ mode: 'open' })
  }

  public ref(selector: string): HTMLElement {
    return this.shadow.querySelector(`[ref=${selector}]`) as HTMLElement
  }

  public query(selector: string) {
    return this.shadow.querySelector(selector)
  }

  public queryAll(selector: string) {
    return this.shadow.querySelectorAll(selector)
  }
}

/**
 * 注册自定义元素
 * @param tag nodeName
 * @param target 自定义元素的类
 */
export function register(target: CustomElementConstructor, childNode: string[]) {
  const tag = Reflect.get(target, 'nodeName') as string
  const name = target.name
  console.log({ tag, name })
  if (!Reflect.has(window, name)) {
    // 将自定义元素类进行全局挂载
    Reflect.set(window, name, target)

    // 注册
    customElements.define(tag, target)

    // 全局 css 设置
    if (Reflect.has(target, 'GLOBAL_CSS')) {
      const style = document.createElement('style')
      style.textContent = Reflect.get(target, 'GLOBAL_CSS')
      document.head.appendChild(style)
    }

    setAllowableChildNode(tag, childNode)
  }
}

export function getConstructor(nodeName: string): Promise<CustomElementConstructor> {
  return new Promise((resolve, reject) => {
    let ctr = customElements.get(nodeName)
    if (ctr) {
      resolve(ctr)
    } else {
      customElements.whenDefined(nodeName).then(() => {
        resolve(customElements.get(nodeName))
      })
    }
  })
}
